﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;

namespace DarkLight.Model
{
    [Serializable]
    [DataContract]
    public class ObservableObject : INotifyPropertyChanged, INotifyPropertyChanging, IDataErrorInfo
    {
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged
        {
            add { propertyChanged += value; }
            remove { propertyChanged -= value; }
        }
        private PropertyChangedEventHandler propertyChanged;
        [SuppressMessage("Microsoft.Design", "CA1030")]
        protected void RaisePropertyChanged(string propertyName)
        {
#if (DEBUG)
            CheckPropertyName(propertyName);
#endif
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (propertyChanged != null) { propertyChanged(this, e); }
        }
        #endregion

        #region INotifyPropertyChanging
        public event PropertyChangingEventHandler PropertyChanging
        {
            add { propertyChanging += value; }
            remove { propertyChanging -= value; }
        }
        private PropertyChangingEventHandler propertyChanging;
        [SuppressMessage("Microsoft.Design", "CA1030")]
        protected void RaisePropertyChanging(string propertyName)
        {
#if (DEBUG)
            CheckPropertyName(propertyName);
#endif
            OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
        }
        protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
                throw new ArgumentNullException("e");
            if (propertyChanging != null) { propertyChanging(this, e); }
        }
        #endregion

        #region IDataErrorInfo
        public bool IsValid
        {
            get { return string.IsNullOrEmpty(Error); }
        }
        
        public string Error
        {
            get
            {
                var errors = TypeDescriptor.GetProperties(this).OfType<PropertyDescriptor>().Select(d => this[d.Name]);
                return string.Join(Environment.NewLine, errors);
            }
        }

        public string this[string columnName]
        {
            get 
            {
                var descriptor = TypeDescriptor.GetProperties(this)[columnName];
                var validations = GetValidations(descriptor);
                var errors = validations.Select(v => !v.IsValid(descriptor.GetValue(this)) ? string.IsNullOrEmpty(v.ErrorMessage) ? "Error" : v.ErrorMessage : "");
                return string.Join(Environment.NewLine, errors);
            }
        }
        #endregion

        #region Private Members
        private void CheckPropertyName(string propertyName)
        {
            PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties(this)[propertyName];
            if (propertyDescriptor == null)
            {
                throw new InvalidOperationException(string.Format(null,
                    "The property with the propertyName '{0}' doesn't exist.", propertyName));
            }
        }

        private static ValidationAttribute[] GetValidations(PropertyDescriptor property)
        {
            var validationAttributes = (ValidationAttribute[])property.Attributes.OfType<ValidationAttribute>().ToArray();
            return validationAttributes;
        }
        #endregion
    }
}
